using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using RestSharp;
using TripleA.Core.Interfaces;
using HttpResponse = TripleA.Core.Interfaces.Entities.HttpResponse;

namespace TripleA.Core.Extensions.Web
{
    public class HttpUriAssert
    {
        public bool Secure { get; set; }
        public string Host { get; set; }
        public int Port { get; set; }
        public string Path { get; set; }
    }

    public class HttpStepConfig
    {
        public string Uri { get; set; }
        /// <summary>
        /// Allows override of default credentials stored in context
        /// </summary>
        public ICredentials Credentials { get; set; }
        public Method Method { get; set; }
        public object Args { get; set; }
        public bool InjectArgsIntoUrl { get; set; }
        public HttpStatusCode ExpectedStatus { get; set; }
        public HttpUriAssert ExpectedUri { get; set; }
        public string ExpectedContentType { get; set; }

        public HttpStepConfig()
        {
            Method = Method.GET;
            ExpectedStatus = HttpStatusCode.OK;
        }

        public static HttpStepConfig Get(string uri, object args)
        {
            return new HttpStepConfig
                       {
                           Args = args,
                           Method = Method.GET,
                           Uri = uri
                       };
        }

        public static HttpStepConfig InjectedGet(string uri, object args)
        {
            var call = Get(uri, args);
            call.InjectArgsIntoUrl = true;
            return call;
        }

        public static HttpStepConfig Post(string uri, object args)
        {
            return new HttpStepConfig
                       {
                           Args = args,
                           Method = Method.POST,
                           Uri = uri
                       };
        }
    }

    public class HttpStep<T> : StepBase<T>
        where T: IWebContext
    {
        private readonly HttpStepConfig _config;

        public HttpStep(HttpStepConfig config)
        {
            _config = config;
        }

        public override void Execute(T context)
        {
            // update the context
            context.Uri = _config.Uri;

            var client = new RestClient();
            if (!string.IsNullOrWhiteSpace(context.UserAgent))
                client.UserAgent = context.UserAgent;

            var request = new RestRequest(_config.Uri, _config.Method);

            if (_config.Credentials != null)
                request.Credentials = _config.Credentials;
            else if (context.Credentials != null)
                request.Credentials = context.Credentials;
            if (_config.Args != null)
            {
                var paramType = _config.InjectArgsIntoUrl
                                    ? ParameterType.UrlSegment
                                    : ParameterType.GetOrPost;

                request.Parameters.AddRange(_config.Args.GetType().GetProperties().Select(
                    pi => new Parameter
                    {
                        Name = pi.Name,
                        Value = pi.GetValue(_config.Args, null),
                        Type = paramType
                    }));
            }

            // handle cookies from the previous response
            var cookies = context.CookieHandler(context);
            if (cookies != null && cookies.Count() > 0)
                cookies.ToList().ForEach(cookie => request.AddCookie(cookie.Name, cookie.Value));

            var response = client.Execute(request);
            context.Response = new HttpResponse
                                   {
                                       Content = response.Content,
                                       Type = response.ContentType,
                                       Length = response.ContentLength,
                                       Uri = response.ResponseUri,
                                       StatusCode = response.StatusCode,
                                       Server = response.Server,
                                       Headers = response.Headers.Select(
                                           hdr => new KeyValuePair<string, object>(hdr.Name, hdr.Value))
                                   };

            AssertHttpStatus(context);
            AsserContentType(context);
            AssertUri(context);
        }

        protected virtual void AsserContentType(T context)
        {
            if (string.IsNullOrWhiteSpace(_config.ExpectedContentType))
                return;

            
            if (!context.Response.Type.StartsWith(_config.ExpectedContentType, StringComparison.OrdinalIgnoreCase)) 
                throw new ApplicationException(string.Format("Expected ContentType '{0}' but got '{1}'",
                    _config.ExpectedContentType,
                    context.Response.Type));
        }

        protected virtual void AssertHttpStatus(T context)
        {
            if (context.Response.StatusCode != _config.ExpectedStatus)
                throw new ApplicationException(string.Format("Expected '{0}' but {1} returned '{2}'",
                    _config.ExpectedStatus,
                    context.Uri,
                    context.Response.StatusCode));            
        }

        protected virtual void AssertUri(T context)
        {
            if (_config.ExpectedUri == null)
                return;

            var expected = _config.ExpectedUri.Secure ? "Https" : "Http";
            if (string.Compare(context.Response.Uri.Scheme, expected, StringComparison.OrdinalIgnoreCase) != 0) 
                throw new ApplicationException(string.Format("Expected scheme '{0}'", expected));

            if (!string.IsNullOrWhiteSpace(_config.ExpectedUri.Path) &&
                (string.Compare(context.Response.Uri.AbsolutePath, _config.ExpectedUri.Path, StringComparison.OrdinalIgnoreCase) != 0))
            {
                throw new ApplicationException(string.Format("Expected path '{0}', actual '{1}'" , 
                    _config.ExpectedUri.Path,
                    context.Response.Uri.AbsolutePath));
            }
        }
    }
}